package cn.geminis.crypto.core.util;

import cn.geminis.crypto.core.x509.X509Certificate;
import cn.geminis.crypto.csp.AbstractCspFactory;
import cn.geminis.crypto.csp.AbstractSigner;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.crypto.io.DigestOutputStream;
import org.bouncycastle.operator.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;

/**
 * @author puddi
 */
public class ProviderUtils {

    public static DigestCalculatorProvider getDigestCalculatorProvider() {
        return digestAlgorithmIdentifier -> {

            var digest = AbstractCspFactory.getDigest(digestAlgorithmIdentifier.getAlgorithm().getId());

            return new DigestCalculator() {

                private DigestOutputStream stream = new DigestOutputStream(digest);

                @Override
                public AlgorithmIdentifier getAlgorithmIdentifier() {
                    return digestAlgorithmIdentifier;
                }

                @Override
                public OutputStream getOutputStream() {
                    return stream;
                }

                @Override
                public byte[] getDigest() {
                    try {
                        return stream.getDigest();
                    } finally {
                        try {
                            digest.close();
                        } catch (IOException e) {
                            System.out.println("释放摘要器资源错误，" + e.toString());
                        }
                    }
                }
            };
        };
    }

    public static ContentVerifierProvider getContentVerifierProvider(X509Certificate cert) {
        return new ContentVerifierProvider() {

            @Override
            public boolean hasAssociatedCertificate() {
                return !Objects.isNull(cert);
            }

            @Override
            public X509CertificateHolder getAssociatedCertificate() {
                return new X509CertificateHolder(cert.getBcCertificate());
            }

            @Override
            public ContentVerifier get(AlgorithmIdentifier verifierAlgorithmIdentifier) {
                return new ContentVerifier() {

                    private ByteArrayOutputStream stream = new ByteArrayOutputStream();

                    @Override
                    public AlgorithmIdentifier getAlgorithmIdentifier() {
                        return verifierAlgorithmIdentifier;
                    }

                    @Override
                    public OutputStream getOutputStream() {
                        return stream;
                    }

                    @Override
                    public boolean verify(byte[] expected) {
                        try (var signer = AbstractCspFactory.getSigner(verifierAlgorithmIdentifier.getAlgorithm().getId())) {
                            var publicKey = hasAssociatedCertificate() ? cert.getPublicKey() : signer.getPublicKey();
                            return signer.verifySignature(stream.toByteArray(), expected, publicKey);
                        } catch (Exception e) {
                            throw new RuntimeException("验证签名错误", e);
                        }
                    }
                };
            }
        };
    }

    public static ContentVerifierProvider getContentVerifierProvider(X509Certificate cert, AbstractSigner signer) {
        return new ContentVerifierProvider() {

            @Override
            public boolean hasAssociatedCertificate() {
                return !Objects.isNull(cert);
            }

            @Override
            public X509CertificateHolder getAssociatedCertificate() {
                return new X509CertificateHolder(cert.getBcCertificate());
            }

            @Override
            public ContentVerifier get(AlgorithmIdentifier verifierAlgorithmIdentifier) {
                return new ContentVerifier() {

                    private ByteArrayOutputStream stream = new ByteArrayOutputStream();

                    @Override
                    public AlgorithmIdentifier getAlgorithmIdentifier() {
                        return verifierAlgorithmIdentifier;
                    }

                    @Override
                    public OutputStream getOutputStream() {
                        return stream;
                    }

                    @Override
                    public boolean verify(byte[] expected) {
                        var publicKey = hasAssociatedCertificate() ? cert.getPublicKey() : signer.getPublicKey();
                        return signer.verifySignature(stream.toByteArray(), expected, publicKey);
                    }
                };
            }
        };
    }

    public static SignerInformationVerifier getSignerInformationVerifier
            (X509Certificate cert) {

        return new SignerInformationVerifier(
                new DefaultCMSSignatureAlgorithmNameGenerator(),
                new DefaultSignatureAlgorithmIdentifierFinder(),
                ProviderUtils.getContentVerifierProvider(cert),
                ProviderUtils.getDigestCalculatorProvider()
        );
    }

    public static SignerInformationVerifier getSignerInformationVerifier
            (X509Certificate cert, AbstractSigner signer) {

        return new SignerInformationVerifier(
                new DefaultCMSSignatureAlgorithmNameGenerator(),
                new DefaultSignatureAlgorithmIdentifierFinder(),
                ProviderUtils.getContentVerifierProvider(cert, signer),
                ProviderUtils.getDigestCalculatorProvider()
        );
    }
}
